package de.rj.urwalking.ui.fragments;

import java.util.ArrayList;

import routing.googleadapter.RoutePartWalkGoogle;
import android.app.Activity;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;

import de.rj.urwalking.R;
import de.rj.urwalking.config.Constants;
import de.rj.urwalking.data.Perspective;
import de.rj.urwalking.db.DBSalientObject;
import de.rj.urwalking.interfaces.ControllableFragment;
import de.rj.urwalking.loader.Check3dDataLoader;
import de.rj.urwalking.loader.SalientObjectLoader;
import de.rj.urwalking.ui.TouchableWrapper;
import de.rj.urwalking.ui.activities.QuestionsActivity;
import de.rj.urwalking.ui.activities.URwalkingActivity;
import de.rj.urwalking.util.ExperimentManager;
import de.rj.urwalking.util.LocationUtil;
import de.rj.urwalking.util.PrefUtil;
import de.rj.urwalking.util.Util;
import de.rose_mobil.models.route.Route;
import de.rose_mobil.models.route.RoutePart;

public class UrwGoogleMapFragment extends SupportMapFragment implements OnMapClickListener,
		ActionMode.Callback, ControllableFragment, OnCameraChangeListener, OnMarkerClickListener {

	private GoogleMap mMap;
	private Marker mMarker;
	private Location mLocation;
	private URwalkingActivity mActivity;

	private ArrayList<Marker> addedMarkers = new ArrayList<Marker>();

	// UI
	private View mOriginalContentView;
	private TouchableWrapper mTouchView;
	private ActionMode mActionMode;

	// route
	private ArrayList<Polyline> mRoutePolylines = new ArrayList<Polyline>();
	private Marker mStartMarker;
	private Marker mDestinationMarker;
	private LoaderCallbacks<Boolean> check3dDataLoaderCallbacks = new Check3dDataLoaderCallbacks();
	private LoaderCallbacks<ArrayList<DBSalientObject>> salientObjectLoaderCallbacks = new SalientObjectLoaderCallbacks();
	private float mOrientation;
	private ArrayList<Marker> mSalientObjects = new ArrayList<Marker>();
	private Location mLastLoadedLocation;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		// workaround to detect touches
		// http://dimitar.me/how-to-detect-a-user-pantouchdrag-on-android-map-v2/
		mOriginalContentView = super.onCreateView(inflater, container, savedInstanceState);

		setUpMapIfNeeded();

		mTouchView = new TouchableWrapper(getActivity());
		mTouchView.addView(mOriginalContentView);
		return mTouchView;

	}

	@Override
	public View getView() {
		return mOriginalContentView;
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);

		try {
			mActivity = (URwalkingActivity) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException(
					"UrwGoogleMapFragment can only be used inside URwalkingActivity!");
		}
	}

	private void setUpMapIfNeeded() {
		// Do a null check to confirm that we have not already instantiated the
		// map.
		if (mMap == null) {
			mMap = getMap();
			// Check if we were successful in obtaining the map.
			if (mMap != null) {
				// The Map is verified. It is now safe to manipulate the map.
				initMap();
			}
		}
	}

	private void initMap() {

		if (getArguments() != null && getArguments().getInt("perspective") != 0) {
			int perspective = getArguments().getInt("perspective");
			mMap.setMapType(perspective);
		} else {
			mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
		}

		mMap.moveCamera(CameraUpdateFactory.zoomTo(18.0f));

		initMapLocation();

		// add padding on top so the ui elements aren't behind the actionbar
		TypedValue tv = new TypedValue();
		int actionBarHeight = 0;
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
			if (getActivity().getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
				actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
						.getDisplayMetrics());
		} else if (getActivity().getTheme().resolveAttribute(
				com.actionbarsherlock.R.attr.actionBarSize, tv, true)) {
			actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
					.getDisplayMetrics());
		}
		mMap.setPadding(0, actionBarHeight, 0, 0);

		// show user position
		mMap.setMyLocationEnabled(true);

		mMap.getUiSettings().setMyLocationButtonEnabled(false);
		mMap.getUiSettings().setZoomControlsEnabled(false);
		mMap.setOnMapClickListener(this);
		mMap.setOnCameraChangeListener(this);
		mMap.setOnMarkerClickListener(this);

	}

	private void initMapLocation() {
		// check if we have a user defined location (test mode)
		if (PrefUtil.inTestMode(getActivity())
				&& !PrefUtil.readBoolean(R.string.key_pref_test_mode_actual_location,
						getActivity(), true)) {
			String locationName = PrefUtil.readString(R.string.key_pref_example_location,
					getActivity(), getString(R.string.default_location));
			mLocation = LocationUtil.getLocationFor(locationName);

			// Move the map so that it is centered on the start position
			LatLng startPoint = new LatLng(mLocation.getLatitude(), mLocation.getLongitude());
			mMap.moveCamera(CameraUpdateFactory.newLatLng(startPoint));

		} else if (getArguments() != null) {
			// check if we got a previous location
			double cameraLat = getArguments().getDouble("cameraLat");
			double cameraLon = getArguments().getDouble("cameraLon");

			if (cameraLat != 0.0 && cameraLon != 0.0) {

				LatLng startPoint = new LatLng(cameraLat, cameraLon);
				mMap.moveCamera(CameraUpdateFactory.newLatLng(startPoint));
			}
		}
	}

	@Override
	public void onMapClick(LatLng position) {

		// remove a previous marker
		if (mMarker != null) {
			mMarker.remove();
		}

		// add a new one
		mMarker = mMap.addMarker(new MarkerOptions().position(position));

		// start actionmode, if not already running
		if (mActionMode == null) {
			mActionMode = ((SherlockFragmentActivity) getActivity()).startActionMode(this);
		}

		// check if there are buildings around the chosen location
		Bundle args = new Bundle();
		args.putDouble("lat", position.latitude);
		args.putDouble("lon", position.longitude);

		LoaderManager lm = getActivity().getSupportLoaderManager();
		lm.restartLoader(Constants.CHECK_3D_DATA_LOADER_ID, args, check3dDataLoaderCallbacks);

	}

	@Override
	public boolean onCreateActionMode(ActionMode mode, Menu menu) {
		MenuInflater inflater = mode.getMenuInflater();
		inflater.inflate(R.menu.outdoor_context_menu, menu);

		// Disable this menu item initially. It will be enabled if there are
		// buildings around.
		menu.findItem(R.id.context_menu_change_perspective).setEnabled(false);

		return true;
	}

	@Override
	public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
		return false;
	}

	@Override
	public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
		switch (item.getItemId()) {
		case R.id.context_menu_navigate_here:
			startLoadingRoute();
			mode.finish();
			return true;
		case R.id.context_menu_change_perspective:
			show3dView();
			mode.finish();
			return true;
		case R.id.context_menu_set_landmark:
			showQuestions();
			return true;
		default:
			return false;
		}
	}

	@Override
	public void onDestroyActionMode(ActionMode mode) {
		mActionMode = null;
		mMarker.remove();
	}

	private void startLoadingRoute() {

		if (mStartMarker != null) {
			mStartMarker.remove();
			mDestinationMarker.remove();
		}

		LatLng startPosition = new LatLng(mLocation.getLatitude(), mLocation.getLongitude());
		mStartMarker = mMap.addMarker(new MarkerOptions().position(startPosition).icon(
				BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)));
		mDestinationMarker = mMap.addMarker(new MarkerOptions().position(mMarker.getPosition())
				.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)));

		Bundle args = new Bundle();
		args.putDouble("startLat", startPosition.latitude);
		args.putDouble("startLon", startPosition.longitude);
		args.putDouble("destinationLat", mMarker.getPosition().latitude);
		args.putDouble("destinationLon", mMarker.getPosition().longitude);

		getActivity().getSupportLoaderManager().restartLoader(Constants.ROUTE_LOADER_ID, args,
				mActivity);
	}

	private void show3dView() {

		JpctFragment fragment = new OutdoorFragment();
		Bundle args = new Bundle();
		args.putDouble("objectLat", mMarker.getPosition().latitude);
		args.putDouble("objectLon", mMarker.getPosition().longitude);
		args.putDouble("cameraLat", mMap.getCameraPosition().target.latitude);
		args.putDouble("cameraLon", mMap.getCameraPosition().target.longitude);
		args.putInt("perspective", Perspective.TYPE_3D_STREET);

		fragment.setArguments(args);

		mActivity.switchFragments(fragment);
	}

	private void showQuestions() {

		if (!ExperimentManager.getInstance().isExperimentRunning()) {
			mActivity.startExperiment();
		}

		DBSalientObject object = new DBSalientObject(mMarker.getPosition(), false);
		Intent intent = new Intent(getActivity(), QuestionsActivity.class);
		intent.putExtra("object", object);
		intent.putExtra("currentLatitude", mLocation.getLatitude());
		intent.putExtra("currentLongitude", mLocation.getLongitude());
		getActivity().startActivityForResult(intent, Constants.QUESTIONS_ACTIVITY_REQUEST_CODE);

	}

	@Override
	public void setLocation(Location location) {
		// do nothing if we don't get a valid location
		if (location == null) {
			return;
		}
		// remember the new location
		mLocation = location;

		// if we moved a significant amount, reload salient objects around new location
		if (LocationUtil.hasMovedSignificantly(location, mLastLoadedLocation)
				&& Util.hasConnection(getActivity())
				&& !getActivity().getSupportLoaderManager().hasRunningLoaders()) {

			mLastLoadedLocation = location;
			getActivity().getSupportLoaderManager().restartLoader(
					Constants.SALIENT_OBJECT_LOADER_ID, null, salientObjectLoaderCallbacks);
			mActivity.updateLocationTextView();
		}

		//
		mMap.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(location.getLatitude(), location
				.getLongitude())));

	}

	@Override
	public void setOrientation(float orientation) {

		mOrientation = orientation;

		if (mMap == null)
			return;

		float newBearing = (float) ((orientation + Math.PI / 2) * 180 / Math.PI);

		// Construct a CameraPosition focusing on the current location and
		// rotate the camera to that position.
		CameraPosition cameraPosition = new CameraPosition.Builder()
				.target(mMap.getCameraPosition().target).zoom(mMap.getCameraPosition().zoom)
				.bearing(newBearing).tilt(mMap.getCameraPosition().tilt).build();

		mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

	}

	@Override
	public void setRoute(Route route) {

		// clear old route
		for (Polyline line : mRoutePolylines) {
			line.remove();
		}
		mRoutePolylines = new ArrayList<Polyline>();

		// add route parts to map and remember them in an ArrayList (so we can
		// remove the route later)
		for (RoutePart rp : route.getRouteParts()) {

			Polyline line = mMap.addPolyline(((RoutePartWalkGoogle) rp).getLineData());
			line.setColor(0xFF99cc00);
			mRoutePolylines.add(line);
		}

	}

	@Override
	public void setObjectName(String name) {

		Marker newMarker = mMap.addMarker(new MarkerOptions().position(mMarker.getPosition())
				.title(name));
		newMarker.showInfoWindow();
		addedMarkers.add(newMarker);

		mActionMode.finish();
	}

	public void setMapType(int type) {

		if (mMap != null) {
			mMap.setMapType(type);
		}
	}

	private class Check3dDataLoaderCallbacks implements LoaderCallbacks<Boolean> {

		public Loader<Boolean> onCreateLoader(int id, Bundle args) {

			mActionMode.getMenu().findItem(R.id.context_menu_change_perspective).setEnabled(false);

			LatLng position = new LatLng(args.getDouble("lat"), args.getDouble("lon"));
			return new Check3dDataLoader(getActivity(), position);

		}

		public void onLoadFinished(Loader<Boolean> loader, Boolean result) {

			// Check if we still are in action mode. If not, trying to
			// enable/disable a menu item would fail.
			if (mActionMode != null) {
				mActionMode.getMenu().findItem(R.id.context_menu_change_perspective)
						.setEnabled(result);
			}
		}

		public void onLoaderReset(Loader<Boolean> loader) {
		}

	}

	private class SalientObjectLoaderCallbacks implements
			LoaderCallbacks<ArrayList<DBSalientObject>> {

		public Loader<ArrayList<DBSalientObject>> onCreateLoader(int id, Bundle args) {

			SalientObjectLoader loader = null;
			if (args != null && args.getBoolean("manual_load")) {
				loader = new SalientObjectLoader(getActivity(), mLastLoadedLocation, mOrientation);
			} else {
				loader = new SalientObjectLoader(getActivity(), mLocation, mOrientation);
			}
			return loader;

		}

		public void onLoadFinished(Loader<ArrayList<DBSalientObject>> loader,
				ArrayList<DBSalientObject> data) {

			removeOldMarkers();

			addNewMarkers(data);

			if (getActivity() instanceof URwalkingActivity) {
				((URwalkingActivity) getActivity()).updateSideBar(data);
			}
		}

		public void onLoaderReset(Loader<ArrayList<DBSalientObject>> loader) {
		}

	}

	private void removeOldMarkers() {

		for (Marker m : mSalientObjects) {
			m.remove();
		}

	}

	private void addNewMarkers(ArrayList<DBSalientObject> data) {
		for (DBSalientObject dbSalientObject : data) {
			dbSalientObject.calculateGpsCoords();

			Marker m = mMap.addMarker(new MarkerOptions().position(dbSalientObject.getPosition())
					.title(dbSalientObject.getName()));

			mSalientObjects.add(m);
		}
	}

	@Override
	public void onCameraChange(CameraPosition position) {

		Location cameraLocation = new Location(LocationManager.GPS_PROVIDER);
		cameraLocation.setLatitude(position.target.latitude);
		cameraLocation.setLongitude(position.target.longitude);

		if (PrefUtil.readBoolean(R.string.key_pref_auto_load, getActivity(), false)
				&& LocationUtil.hasMovedSignificantly(cameraLocation, mLastLoadedLocation)) {

			mLastLoadedLocation = cameraLocation;

			if (Util.hasConnection(getActivity()) && !getLoaderManager().hasRunningLoaders()) {
				Bundle args = new Bundle();
				args.putBoolean("manual_load", true);
				getActivity().getSupportLoaderManager().restartLoader(
						Constants.SALIENT_OBJECT_LOADER_ID, args, salientObjectLoaderCallbacks);
			}
		}
	}

	@Override
	public boolean onMarkerClick(Marker m) {

		mMarker = m;

		// start actionmode, if not already running
		if (mActionMode == null) {
			mActionMode = ((SherlockFragmentActivity) getActivity()).startActionMode(this);
		}
		return false;
	}

}
